"""
@File    :   service.py
@Time    :   2024/03/20 12:33:29
@Author  :   Qrj
@Email   :   923207736@qq.com
@description   :   OAuth2密码模式认证相关业务逻辑
"""

from ipaddress import IPv4Network

from redis.asyncio import Redis
from sqlalchemy.ext.asyncio import AsyncSession

from src.business.admin.system.oauth2.auth.enums import (
    OAuth2GrantTypeEnum,
    UserLoginPasswordEncryptTypeEnum,
)
from src.business.admin.system.oauth2.auth.password.schemas.validate import (
    PasswordCreateDoubleTokenValidateSchema,
    SwaggerPasswordCreateDoubleTokenValidateSchema,
)
from src.business.admin.system.oauth2.client.schemas.out import (
    Oauth2ClientCompleteOutSchema,
)
from src.business.admin.system.oauth2.client.service import Oauth2ClientService
from src.business.admin.system.oauth2.token.schemas.out import (
    DoubleTokenCompleteOutSchema,
)
from src.business.admin.system.oauth2.token.schemas.validate import (
    DoubleTokenCreateValidateSchema,
)
from src.business.admin.system.oauth2.token.service import TokenService
from src.business.admin.system.tenant.tenant.schemas.out import TenantCompleteOutSchema
from src.business.admin.system.tenant.tenant.service import TenantService
from src.business.admin.system.user.schemas.out import (
    UserCompleteAndHavePasswordHashOutSchema,
)
from src.business.admin.system.user.service import UserService
from src.exceptions.custom_async_validator import generate_async_validator_exception
from src.utils.password_encryption import PswHashUtil
from src.utils.rsa_encryption import RsaEncryptionUtil


class PasswordService:
    """OAuth2密码模式认证业务逻辑类"""

    @staticmethod
    async def user_login_password_decrypt(
        password: str,
        encrypt_type: UserLoginPasswordEncryptTypeEnum,
        field_name: str = "password",
    ) -> str:
        """对用户登录密码密文进行解密

        Args:
            password (str): 密码密文
            encrypt_type (UserLoginPasswordEncryptTypeEnum): 加密类型
            field_name (str): 字段名称

        Returns:
            str: 解密后的密码
        """
        # 根据加密类型进行解密
        try:
            if encrypt_type is UserLoginPasswordEncryptTypeEnum.rsa:
                # rsa解密
                password = await RsaEncryptionUtil.async_rsa_decrypt(password)
            elif encrypt_type is UserLoginPasswordEncryptTypeEnum.sm2:
                # sm2解密
                pass
            return password
        except Exception:
            # 解密失败则直接返回密码错误的响应信息
            raise generate_async_validator_exception(field_name, "用户的登录密码错误")

    @staticmethod
    async def user_login(
        user_password_hash: str,
        login_password: str,
        user_can_login_ip_network: list[IPv4Network] | None = None,
        login_ip: str | None = None,
    ) -> bool:
        """使用用户名进行用户登录

        Args:
            user_password_hash (str): 用户的密码hash
            user_can_login_ip_network (list[IPv4Network]): 用户的的可登录IP段
            login_password (str): 请求登录的密码
            login_ip (str | None, optional): 请求登录的IP

        Returns:
            bool: 是否成功登录
        """
        # 验证密码hash
        password_verify_result = await PswHashUtil.async_verify_psw(
            login_password, user_password_hash
        )
        if password_verify_result and login_ip is not None:
            # 密码hash验证成功
            # 判断登录IP是否符合要求
            return UserService.validate_ip_can_login(
                login_ip, user_can_login_ip_network
            )
        elif password_verify_result:
            return True
        else:
            # 验证失败, 直接报错
            raise generate_async_validator_exception("password", "用户的登录密码错误")

    @staticmethod
    async def create_swagger_password_double_token(
        db_session: AsyncSession,
        token_redis_pool: Redis,
        create_schema: SwaggerPasswordCreateDoubleTokenValidateSchema,
        login_ip: str,
    ) -> DoubleTokenCompleteOutSchema:
        # 验证租户是否存在
        tenant: TenantCompleteOutSchema = await TenantService.get_tenant_use_id(
            db_session, create_schema.tenant_id
        )
        # 验证OAuth2客户端是否存在
        oauth2_client: Oauth2ClientCompleteOutSchema = (
            await Oauth2ClientService.get_oauth2_client_use_client_id(
                db_session,
                create_schema.tenant_id,
                create_schema.oauth2_client_client_id,
            )
        )
        # 验证客户端密钥是否正确
        Oauth2ClientService.validate_oauth2_client_secret(
            oauth2_client.client_secret,
            create_schema.oauth2_client_client_secert,
            field_name="oauth2_client_client_secert",
        )
        # 验证是否允许客户端使用当前授权模式
        Oauth2ClientService.validate_oauth2_client_authorized_grant_type(
            oauth2_client.authorized_grant_types, OAuth2GrantTypeEnum.swagger_password
        )
        # 获取用户信息
        user: UserCompleteAndHavePasswordHashOutSchema = (
            await UserService.get_user_use_username(
                db_session,
                tenant.id,
                username=create_schema.username,
                need_password_hash=True,
            )
        )
        # 验证用户登录
        await PasswordService.user_login(
            user.password_hash,
            create_schema.password,
            user_can_login_ip_network=user.can_login_ip_network,
            login_ip=login_ip,
        )
        # 验证成功后返回双Token
        # 密码模式的双Token需要用户信息, 但无需scopes
        create_double_token_schema = DoubleTokenCreateValidateSchema(
            tenant_id=tenant.id,
            tenant_is_system=tenant.is_system,
            tenant_pakcage_id=tenant.tenant_package_id,
            oauth2_client_id=oauth2_client.id,
            oauth2_grant_type=OAuth2GrantTypeEnum.swagger_password.value,
            access_token_expiration_seconds=oauth2_client.access_token_expiration_seconds,
            refresh_token_expiration_seconds=oauth2_client.refresh_token_expiration_seconds,
            user_id=user.id,
            username=user.username,
            user_is_admin=user.is_admin,
            user_number_of_simultaneous_logins=user.number_of_simultaneous_logins,
        )
        double_token = await TokenService.create_double_token(
            db_session, token_redis_pool, create_double_token_schema
        )
        return double_token

    @staticmethod
    async def create_password_double_token(
        db_session: AsyncSession,
        token_redis_pool: Redis,
        create_schema: PasswordCreateDoubleTokenValidateSchema,
        login_ip: str,
    ) -> DoubleTokenCompleteOutSchema:
        # 验证租户是否存在
        tenant: TenantCompleteOutSchema = await TenantService.get_tenant_use_id(
            db_session, create_schema.tenant_id
        )
        # 验证OAuth2客户端是否存在
        oauth2_client: Oauth2ClientCompleteOutSchema = (
            await Oauth2ClientService.get_oauth2_client_use_client_id(
                db_session,
                create_schema.tenant_id,
                create_schema.oauth2_client_client_id,
            )
        )
        # 验证客户端密钥是否正确
        Oauth2ClientService.validate_oauth2_client_secret(
            oauth2_client.client_secret,
            create_schema.oauth2_client_client_secert,
            field_name="oauth2_client_client_secert",
        )
        # 验证是否允许客户端使用当前授权模式
        Oauth2ClientService.validate_oauth2_client_authorized_grant_type(
            oauth2_client.authorized_grant_types, OAuth2GrantTypeEnum.password
        )
        # 获取用户信息
        user: UserCompleteAndHavePasswordHashOutSchema = (
            await UserService.get_user_use_username(
                db_session,
                tenant.id,
                username=create_schema.username,
                need_password_hash=True,
            )
        )
        # 密码密文解密
        password = await PasswordService.user_login_password_decrypt(
            create_schema.password, create_schema.encrypt_type
        )
        # 验证用户登录
        await PasswordService.user_login(
            user.password_hash,
            password,
            user_can_login_ip_network=user.can_login_ip_network,
            login_ip=login_ip,
        )
        # 验证成功后返回双Token
        # 密码模式的双Token需要用户信息, 无需scopes, 使用
        create_double_token_schema = DoubleTokenCreateValidateSchema(
            tenant_id=tenant.id,
            tenant_is_system=tenant.is_system,
            tenant_pakcage_id=tenant.tenant_package_id,
            oauth2_client_id=oauth2_client.id,
            oauth2_grant_type=OAuth2GrantTypeEnum.password.value,
            access_token_expiration_seconds=oauth2_client.access_token_expiration_seconds,
            refresh_token_expiration_seconds=oauth2_client.refresh_token_expiration_seconds,
            user_id=user.id,
            username=user.username,
            user_is_admin=user.is_admin,
            user_number_of_simultaneous_logins=user.number_of_simultaneous_logins,
        )
        double_token = await TokenService.create_double_token(
            db_session, token_redis_pool, create_double_token_schema
        )
        return double_token
